import moment from 'moment-mini';
import AppSession from '../services/Support/AppSession';
import AppToken from '../services/Support/AppToken';
import { http } from '../utils/http';
import { usePopError } from '../hooks/global/usePopError';
import { useMessageBox } from '../hooks/global/useMessageBox';
import { useMessage } from '../hooks/global/useMessage';
import { HttpConfig } from '@/config/http';
import type { JcToken } from '@/api/entities/account';
import router from '@/router';
import { RefreshTokenUrl } from '@/config/jctech';
import { AppSoftSign } from '@/config';

export interface JCRequestOption {
  cacheKey?: string;
  isSerialize?: boolean;
  contentType?: string;
  timeout?: number;
  /**
   * 是否静音模式，静音时不弹出任何提示信息
   */
  silent?: boolean;
}

interface IAjaxResponseEntity<T = any> {
  Status: number;
  Info: string;
  Data: T;
  moreInfo?: string;
  ParamsExs?: string;
}

function ShowServerError(url, message, option: JCRequestOption = {}) {
  if (option.silent) {
    console.error(message);
    return;
  }
  if (isSaveUrl(url)) {
    useMessageBox().confirm(message, '保存失败', {
      showCancelButton: false,
      type: 'warning'
    });
    return;
  }
  useMessage().error(message);
}

function isSaveUrl(url) {
  const path = url.split('/').pop();
  return (
    (path.toLowerCase().indexOf('save') === 0 || path.toLowerCase().indexOf('vsave') === 0) && path !== 'SaveModelType'
  );
}

function logOut() {
  AppToken.setToken();
  const route = router.currentRoute.value;
  router.push({
    path: `/login?redirect=${route.path}`
  });
}

/**
 * 保证全局唯一 缓存刷新token的请求 解决并发请求 都需要刷新token的问题;
 */
let RefreshTokenPromise: any = null;
let lastShowNetWorkErrorTime: any = null;

export class ServiceBase {
  static instance: any = null;

  /**
   * 单例调用,暂不支持构造函数需参数的形式.
   * @warning 禁止调用 ServiceBase.getInstance() 会影响子类的 原型链 不再实例化子类的实例;
   */
  static getInstance<T>(...arg): T {
    if (this.instance === null) {
      this.instance = new this();
    }
    return this.instance;
  }

  private getUrl(url) {
    return url;
  }

  // 检测是否需要获取新token
  private __checkOrRefreshToken(token: JcToken, url) {
    // 是否是不需要token的请求
    if (
      Array.isArray(HttpConfig.noTokenUrl) &&
      HttpConfig.noTokenUrl.some((urlTest) => urlTest.test && urlTest.test(url))
    ) {
      return Promise.resolve(null);
    }

    // 如果已经发起过刷新Token请求, 则直接使用;
    if (RefreshTokenPromise) {
      return RefreshTokenPromise;
    }
    // 两分钟内超时 或者已经过期 则重新获取token
    if (
      token &&
      token.RefreshToken &&
      moment().add('5', 'm').isAfter(token.TokenExpire) &&
      moment().isBefore(token.RefreshTokenExpire)
    ) {
      RefreshTokenPromise = this.refreshToken(token)
        .then((token) => {
          RefreshTokenPromise = null;
          AppToken.setToken(token);
          return token;
        })
        .catch((e) => {
          console.error('refreshToken fail');
          RefreshTokenPromise = null;
          logOut();
          throw e;
        });
      return RefreshTokenPromise;
    }
    return Promise.resolve(token);
  }

  private refreshToken(token) {
    return http.post(RefreshTokenUrl, { refreshToken: token.RefreshToken }).then((response) => {
      if (response && response.data && response.data.Status === 1) {
        return response.data.Data;
      }
      throw new Error('刷新Token失败');
    });
  }

  /**
   * 此请求方法不再提供缓存功能
   */
  private requestBase<T>(_url: string, data?: any, option: JCRequestOption = {}): Promise<T> {
    const url = this.getUrl(_url);
    const timeout = option.timeout || 120000; // 默认超时2分钟
    const headers: any = {
      SoftSign: AppSoftSign,
      ContentType: 'application/json'
    };
    const sessionId = AppSession.getSessionId();
    // 本地接口 默认带上  带上一个自生成的 sessionId 解决 使用iframe 嵌套系统时，不能发送cookie 的情况
    if (sessionId && url.indexOf('http') !== 0) {
      headers.sessionId = sessionId;
    }
    // 参数是否转义；
    if (option.isSerialize) {
      data = encodeURIComponent(JSON.stringify(data));
    }
    if (option.contentType) {
      headers['Content-Type'] = option.contentType;
    }

    const token: JcToken = AppToken.getToken();
    return this.__checkOrRefreshToken(token, url)
      .then((token) => {
        // 没有token 过期时间 或者 还没过期
        if (token && token.Token && (!token.TokenExpire || moment().isBefore(token.TokenExpire))) {
          headers.token = token.Token;
        }
      })
      .then((next) => {
        return http.post(url, data, { headers, timeout });
      })
      .then((context) => {
        const response: IAjaxResponseEntity<T> = context.data || {};
        AppSession.handleResponseHeader(context.headers || {});
        if ((response && response.Status === 1) || url.includes('v-mcweb')) {
          return response ? response.Data : null;
        }
        const err: any = new Error('服务器返回错误');
        err.data = response;
        throw err;
      })
      .catch((err) => {
        const response: IAjaxResponseEntity = (err && err.data) || {};
        const responseCode = response.Status === -2 ? 403 : err && err.status ? err.status : 500;
        const errorMessage = `(${responseCode} : ${url})`;
        console.log('response', response);

        if (response.Status === -2) {
          if (!url.includes('login') && !url.includes('logout')) {
            logOut();
          }
          throw new Error('登录已超时，请重新登录');
        }
        // 服务器报错，状态为 -1
        if (response.Status === -1) {
          if (!option.silent) {
            usePopError({
              Url: url,
              IsShell: false,
              ServerAddress: window.location.host, // '192.168.9.160:8874',
              BackInfo: response.moreInfo,
              // @ts-expect-error
              ParamsExs: response.paramsExs ? response.paramsExs : response.ParamsExs,
              Env: navigator.appVersion,
              ErrorInfo: response.Info,
              RequestData: typeof data === 'string' ? decodeURIComponent(data) : data
            });
          } else {
            console.error(response.Info);
          }
        } else if (response.Info) {
          // 服务器报错，状态不为-1；
          // 如果服务端 有返回错误信息，则显示出来；
          const message = response.Info.length < 150 ? response.Info : `服务器返回错误信息。${errorMessage}`;
          ShowServerError(url, message, option);
          throw err;
        } else if (
          // 其他情况 检查是否需要弹窗提示。
          (isSaveUrl(url) || !lastShowNetWorkErrorTime || lastShowNetWorkErrorTime < new Date().getTime()) &&
          !url.includes('Monitor') &&
          !option.silent
        ) {
          ShowServerError(url, `请求服务器失败，请检查网络是否畅通，服务端是否已运行。${errorMessage}`, option);
          lastShowNetWorkErrorTime = new Date().getTime() + 15000;
        }
        // 无法处理, 重新抛出错误;
        throw err;
      });
  }

  /**
   * 请求并全局缓存
   * @param url
   * @param data
   * @param option
   */
  public request<T>(url: string, data?: any, option: JCRequestOption = {}): Promise<T> {
    return this.requestBase<T>(url, data, option);
  }
}
